/*
 * @(#)DefaultInvocationFuture.java $version 2010. 8. 11
 *
 * Copyright 2007-2010 NHN Corp. All rights Reserved. 
 * NHN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package com.nhncorp.usf.core.invoker;

import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicInteger;

import com.nhncorp.usf.core.invoker.timeout.SimpleTimeoutHandler;
import com.nhncorp.usf.core.invoker.timeout.TimeoutHandler;

/**
 * The Class DefaultInvocationFuture implements InvocationFuture
 * 
 * @author Web Platform Development Team
 */
public class DefaultInvocationFuture implements InvocationFuture {
	private InvocationFutureListener firstListener;
	private List<InvocationFutureListener> otherListeners;
	private volatile boolean ready;
	private Object returnValue;
	private Exception exception;
	private int waiters;

	private final TimeoutHandler timeoutHandler;

	private static Timer callTimer = null;
	private static final AtomicInteger REFERENCE_COUNTER = new AtomicInteger(0);

	private TimeoutTask timeoutTask;

	/**
	 * DefaultInvocationFuture를 초기화 시킵니다. 
	 */
	public static synchronized void init() {
		REFERENCE_COUNTER.incrementAndGet();
		if (callTimer == null) {
			callTimer = new Timer("DefaultInvocationFuture Timer", true);
		}
	}

	/**
	 * DefaultInvocationFuture생성시 초기화 시킨 값을 복원 시킵니다. 
	 */
	public static synchronized void destroy() {
		final int count = REFERENCE_COUNTER.decrementAndGet();
		if (count == 0 && callTimer != null) {
			callTimer.purge();
			callTimer.cancel();
			callTimer = null;
		}
	}

	/**
	 * DefaultInvocationFuture constructor
	 */
	public DefaultInvocationFuture() {
		this(Long.MAX_VALUE);
	}

	/**
	 * DefaultInvocationFuture constructor
	 * 
	 * @param timeout invoke시 설정할 timeout시간 
	 */
	public DefaultInvocationFuture(Long timeout) {
		this(timeout, SimpleTimeoutHandler.SIMPLE_TIMEOUT_HANDLER);
	}

	/**
	 * DefaultInvocationFuture constructor
	 *
	 * @param timeout invoke시 설정할 timeout시간
	 * @param timeoutHandler timeout을 Exception을 생성하는 클래스
	 */
	public DefaultInvocationFuture(final long timeout, final TimeoutHandler timeoutHandler) {
		if (timeout <= 0) {
			throw new IllegalArgumentException("timeout must not be negative timeout:" + timeout);
		}
		if (timeoutHandler == null) {
			throw new NullPointerException("timeoutHandler");
		}
		// 반드시 timeouthandler를 먼저 등록해야 한다.
		// gc로 인해 scheduler가 먼저 가동될수 있음. 아니면 timeout 시간이 아주 짧은 경우.
		this.timeoutHandler = timeoutHandler;

		if (timeout == Long.MAX_VALUE) {
			// 무한대기 timer에 등록하지 않음.
			return;
		}
		this.timeoutTask = new TimeoutTask();
		if (callTimer == null) {
			// TODO reference count의 증가 일어남. 검토 필요. 
			init();
		}
		callTimer.schedule(timeoutTask, timeout);
	}

	/**
	 * {@inheritDoc}
	 */
	public InvocationFuture addListener(final InvocationFutureListener listener) {
		boolean notifyNow = false;
		synchronized (this) {
			if (this.ready) {
				notifyNow = true;
			} else {
				if (firstListener == null) {
					firstListener = listener;
				} else {
					if (otherListeners == null) {
						otherListeners = new ArrayList<InvocationFutureListener>(1);
					}
					otherListeners.add(listener);
				}
			}
		}

		if (notifyNow) {
			InvocationFutureListeners.notify(this, listener);
		}

		return this;
	}

	/**
	 * {@inheritDoc}
	 */
	public synchronized InvocationFuture removeListener(InvocationFutureListener listener) {
		if (!ready) {
			if (listener == firstListener) {
				if (otherListeners != null && !otherListeners.isEmpty()) {
					firstListener = otherListeners.remove(0);
				} else {
					firstListener = null;
				}
			} else if (otherListeners != null) {
				otherListeners.remove(listener);
			}
		}
		return this;
	}

	/**
	 * Notify listeners.
	 */
	private void notifyListeners() {
		if (firstListener != null) {
			InvocationFutureListeners.notify(this, firstListener);
			firstListener = null;

			if (otherListeners != null) {
				for (InvocationFutureListener listener : otherListeners) {
					InvocationFutureListeners.notify(this, listener);
				}
				otherListeners = null;
			}
		}
	}

	public synchronized boolean isReady() {
		return ready;
	}

	/**
	 * invoke시의 Ready 상태를 설정합니다. 
	 * ready상태를 false시키게 되면 이전에 설정한 timeout을 무시합니다. 
	 * 
	 * @param value the new ready
	 */
	public void setReady(Object value) {
		isReadyAndSet(value);
	}

	public boolean isReadyAndSet(Object value) {
		synchronized (this) {
			if (ready) {
				return false;
			}

			if (this.timeoutTask != null) {

				this.timeoutTask.cancel();
				// 로컬변수에 복사를 한후 null체크를 해야함.
				final Timer timer = callTimer;
				if (timer != null) {
					// purge를 해야 하나. cpu 코스트가 좀 들어갈것으로 보임.
					timer.purge();
				}
				this.timeoutTask = null;
			}

			if (value instanceof Exception) {
				this.exception = (Exception)value;
			} else {
				this.returnValue = value;
			}

			ready = true;
			if (waiters > 0) {
				notifyAll();
			}
		}

		notifyListeners();
		return true;
	}



	/**
	 * timeout시의 행동을 설정합니다.
	 */
	private void actionWhenTimeout() {
		setReady(timeoutHandler.timeout());
	}

	/**
	 * {@inheritDoc}
	 */
	public synchronized InvocationFuture await() throws InterruptedException {
		waiters++;
		try {
			while (!isReady()) {
				wait();
			}
		} finally {
			waiters--;
		}

		return this;
	}

	/**
	 * {@inheritDoc}
	 */
	public synchronized InvocationFuture awaitUninterruptibly() {
		waiters++;
		while (!isReady()) {
			try {
				wait();
			} catch (InterruptedException e) {
				// empty
			}
		}
		waiters--;

		return this;
	}

	/**
	 * {@inheritDoc}
	 */
	public synchronized Object getReturnValue() throws Exception {
		if (exception != null) {
			throw exception;
		}
		return returnValue;
	}

	public synchronized Exception getException() {
		return exception;
	}

    /**
	 * The Class TimeoutTask.
	 */
	private class TimeoutTask extends TimerTask {
		@Override
		public void run() {
			actionWhenTimeout();
		}
	}
}

